package com.dream.learn.myutils;

import cn.hutool.core.collection.CollectionUtil;
import org.apache.commons.lang3.StringUtils;
import org.junit.Ignore;

import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author 凉小枫
 * @detail
 * @date 2021-12-29 17:31
 */
public class StreamUtils {

    public static void main(String[] args) {

        System.out.println("开始》》》》》》》》》》》》》》》》》》》》");
        arrayToCollector();
        List<Map<String, String>> maps = getMap();
        maps.forEach(m -> System.out.println("map::::" + m));

        List<User> userList = getUser();//获取入参对象
        List<String> ids = getIds();//获取入参ids

        /**
         * 进行判空操作
         * */
        boolean empty = CollectionUtil.isEmpty(ids);//对象判空

        /**
         * 进行求和操作
         * */
        stream2Sum(userList);

        /**
         * 进行对象组装 -- stream流 to map方法
         * */
        streamToMap(userList, ids);

        /**
         * 进行对象组装 -- stream流 to List方法
         * */
        streamToList(userList, ids);

        System.out.println("《《《《《《《《《《《《《《《《《《《《结束");
    }

    /**
     * stream流 -->进行求和操作
     */
    private static void stream2Sum(List<User> userList) {
        long count1 = userList.stream()//获取list<对象>中某行数量
            .map(User::getAge).count();
        //对字段进行integer添加
        Integer sum1 = userList.stream().mapToInt(User::getAge).sum();
//        Integer sum2 = userList.stream().collect(Collectors.summingInt(User::getAge)); 不建议使用，可优化
        //进行big decimal添加
//        reduce(BigDecimal.ZERO, BigDecimal::add);
    }

    /**
     * stream进行对象组装  --> 转化为List操作
     */
    private static void streamToList(List<User> userList, List<String> ids) {
        List<String> collect = userList.stream().map(User::getId).collect(Collectors.toList());
        //1、过滤年纪为10的对象
        List<User> collect1 = userList.stream()
            .filter(user -> user.getAge() == 11).collect(Collectors.toList());
        System.out.println("streamToList-过滤某个参数的:" + collect1);

        // 2、 userList与ids的交集
        List<User> collect2 = userList
            .stream() //获取第一个集合的Stream1
            .filter(  //取出Stream1中符合条件的元素组成新的Stream2，lambda表达式1返回值为true时为符合条件
                a ->  //lambda表达式1，a为lambda表达式1的参数，是Stream1中的每个元素
                    ids.stream() //获取第二个集合的Stream3
//                        .map(ClassB::getId) //将第二个集合每个元素的id属性取出来，映射成新的一个Stream4
                        .anyMatch( //返回值（boolean）：Stream4中是否至少有一个元素使lambda表达式2返回值为true
                            id -> //lambda表达式2，id为lambda表达式2的参数，是Stream4中的每个元素
                                Objects.equals(a.getId(), id) //判断id的值是否相等
                        )
            ).collect(Collectors.toList()); //将Stream2转换为List
        System.out.println("streamToList-求交集:" + collect2);

        // 3、求差集 userList与ids的差集
        List<User> collect3 = userList.stream()
            .filter(
                b -> ids.stream()
//                    .map(User::getId)
                    .noneMatch(
                        id ->
                            Objects.equals(b.getId(), id)
                    )
            ).collect(Collectors.toList());
        System.out.println("streamToList-求差集:" + collect3);

        //4、过滤另一个集合存在的数据，userlist过滤ids --相当于交集  取反相当于差
        AtomicInteger i4 = new AtomicInteger(0);
        List<User> collect4 = userList.stream()
            .filter(user ->
                ids.contains(user.getId())
            ).collect(Collectors.toList());
        System.out.println("streamToList-过滤求交集：" + collect4);
    }

    /**
     * 进行对象组装 -- stream流 to map方法
     */
    private static void streamToMap(List<User> userList, List<String> ids) {
        /** 进行对象组装 */
        //1、指定key-value，value是对象某一个字段属性值
        Map<String, String> collect1 = userList.stream().collect(
            Collectors.toMap(User::getId, User::getName));

        //2、指定key-value，value是对象本身，User->User 是一个返回本身的lambda表达式
        Map<String, User> collect2 = userList.stream().collect(
            Collectors.toMap(User::getId, User -> User));

        //3、指定key-value，value是对象本身，Function.identity()是简洁写法，也是返回对象本身
        Map<String, User> collect3 = userList.stream().collect(Collectors.toMap(User::getId, Function.identity()));
        new ArrayList<>(collect3.values()).forEach(c -> {
            System.out.println("streamToMap-collect::" + c);
        });

        //4、指定key-value,value是对象本身，有hash冲突时取第二个key
        Map<String, User> collect4 = userList.stream().collect(
            Collectors.toMap(User::getId, Function.identity(), (key1, key2) -> key2));
        /**
         * 注：解决hash冲突的四个小方法
         * 1.开放定址法（线性探测再散列，二次探测再散列，伪随机探测再散列）
         * 2.再哈希法 （顾名思义，有冲突时再进行一次hash计算）
         * 3.链地址法(Java hashmap就是这么做的) ->冲突得元素后面以链表形式尾插到前一个元素后面
         * 4.建立一个公共溢出区
         */

        //5、指定key-value，value是对象字段属性，重复时将前面和后面的value拼接起来
        Map<String, String> collect5 = userList.stream()
            .sorted(Comparator.comparing(User::getId))
            .collect(Collectors.toMap(User::getId, User::getName, (key1, key2) -> key1 + "," + key2));

        //6、指定key-value，value是集合,重复时将重复key的数据组成集合
        Map<String, List<String>> collect6 = userList.stream().collect(Collectors.toMap(User::getId,
            use -> {
                List<String> strings = new ArrayList<>();
                strings.add(use.getName());
                return strings;
            },
            (List<String> value, List<String> value2) -> {
                value.addAll(value2);
                return value;
            }
        ));

        collect6.forEach((key, value) -> {
            System.out.println("streamToMap-collect6=" + key + ":" + value);
        });

        /** 集合包含另一个集合的id数量 */
        AtomicInteger count = new AtomicInteger();
        ids.forEach(id -> {
            Optional.ofNullable(collect2.get(id)).ifPresent(m ->
                count.getAndIncrement()
            );
        });
        System.out.println("streamToMap-集合包含另一个集合的id数量:::::" + count);

    }

    private static void arrayToCollector() {
        /**
         * 数组的定义方式
         * 1、声明  String[] str;
         * 2、分配空间 str = new String[16];这是固定的
         * 3、赋值 for(i=0;i<16)
         *
         * 可以一起进行
         * String[] str = {"元素1","元素2"};
         * 等同
         * String[] str = new String[]{"元素1","元素2"};
         */
        String[] split = new String[16];
        //看看初始化数组的大小和内容
        System.out.println("new String[16]的长度::::::::" + split.length);//16
        for (int i = 0; i < split.length; i++) System.out.println("初始化【" + i + "】内容==" + split[i]);//16个null

        //赋值前几个
        for (int i = 0; i <= 7; i++) split[i] = String.valueOf(i);

        //赋值的元素有值了
        System.out.println("new String[16]赋值后的长度::::::::" + split.length);//16
        for (int i = 0; i < split.length; i++) System.out.println("赋值后【" + i + "】内容==" + split[i]);

        List<String> stringList = Arrays.stream(split).collect(Collectors.toList());

        List<Long> longList = Arrays.stream(split).map(s ->
            StringUtils.isNotEmpty(s) ? Long.valueOf(s.trim()) : 0L
        ).collect(Collectors.toList());
        System.out.println("数组转化为stringList后的长度::::::::" + stringList.size());//16
        stringList.forEach(System.out::println);
        System.out.println("数组转化为longList后的长度::::::::" + longList.size());//16
        longList.forEach(System.out::println);

    }

    @Ignore
    @Deprecated
    public static List<Map<String, String>> getMap() {
        List<Map<String, String>> objects = new ArrayList<>(4);
        Map<String, String> map1 = new HashMap<>();
        Map<String, String> map2 = new HashMap<>();
        Map<String, String> map3 = new HashMap<>();

        map1.put("id", "1001");
        map1.put("name", "小一");
        map1.put("age", "11");

        map2.put("id", "1002");
        map2.put("name", "小二");
        map2.put("age", "12");

        map3.put("id", "1003");
        map3.put("name", "小三");
        map3.put("age", "13");
//        for (Map<String, String> object : objects) {
//            object.put("id", "1001");
//            object.put("name", "小一");
//            object.put("age", "10");
//        }
        objects.add(map1);
        objects.add(map2);
        objects.add(map3);
        return objects;
    }

    /**
     * 自建对象信息
     */
    public static List<User> getUser() {

        User user1 = new User("1001", "小一", 11);
        User user2 = new User("1002", "小二", 12);
        User user3 = new User("1003", "小三", 13);
        System.out.println("原生equals方法关于自建对象的hash值大小：" + user1.hashCode());//240650537
        System.out.println("原生equals方法关于自建对象的hash值大小：" + user2.hashCode());//483422889
        System.out.println("原生equals方法关于自建对象的hash值大小：" + user3.hashCode());//2088051243

        List<User> users = new ArrayList<>(8);
        users.add(user1);
        users.add(user2);
        users.add(user3);
        return users;
    }

    /**
     * 自建ids
     */
    public static List<String> getIds() {
        List<String> idList = new ArrayList<>();
        idList.add("1001");
        idList.add("1002");
        idList.add("1004");
        return idList;
    }

    /**
     * 内部对象类.
     */
    static class User {
        private String id;
        private String name;
        private Integer age;

        public User() {
        }

        public User(String id, String name, Integer age) {
            this.id = id;
            this.name = name;
            this.age = age;
        }

        public String getId() {
            return id;
        }

        public void setId(String id) {
            this.id = id;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public Integer getAge() {
            return age;
        }

        public void setAge(Integer age) {
            this.age = age;
        }

        @Override
        public String toString() {
            return "User{"
                + "id='" + id + '\''
                + ", name='" + name + '\''
                + ", age='" + age + '\''
                + '}';
        }


        /**
         * 1、什么是hash冲突
         * 我们知道hashmap底层是数组+链表/红黑树，当我们put(key,value)时，需要通过散列函数判断key元素应该放到数组哪个位置，
         * 当不同的元素放到同一个位置时，后放入的数据会以链表的形式插入到前一个元素的尾部，这时我们称发生了hash冲突
         * 注意：此hash冲突不一定是hash值相同，可能是对数组长度取余后的结果相同
         *
         * 2、hash算法是如何解决hash冲突的
         * <code1>
         * //下面我们来看hashMap添加元素时进行的操作
         * public V put(K key, V value) {
         * return putVal(hash(key), key, value, false, true);
         * }
         * static final int hash(Object key) {
         * int h;
         * // 判断key是否为null, 如果为null,则直接返回0;这也是hash map中的key可以为空的原因
         * // 如果不为null，则返回(h = key.hashCode()) ^ (h >>> 16)的执行结果
         * return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
         * }
         * </code1>
         * 第一步：
         * key.hashCode()进行的操作就是根据key值计算出一个int值
         * 至于hashCode()是如何根据key计算出hashcode值的，要分几种情况进行分析:
         * <code2>
         * 例：String的hashCode算法
         * char val[] = value;
         * for (int i = 0; i < value.length; i++) {
         * h = 31 * h + val[i];
         * }
         *
         * </code2>
         * <p1>
         * 补充：为什么用31作为优质乘数?
         * 第一.是因为是质数减少了哈希冲突，
         * 第二.因为首先选择其他偏大的质数同时如果存储数据类型是int的话容易导致溢出，int最大
         * 如果选择偏小的质数那么容易造成较大概率的哈希冲突，
         * 还有一个问题就是为什么不选择31周围的其他数字，比如37和29这些经过测验哈希冲突也不高的质数，
         * 就是基于jvm优化考虑的了，因为jvm可以把i*31优化为不含有乘法运算的i<<5 -i，
         * 但是如果29和37这些质数就算通过jvm优化之后照样含有乘法运算在里面，所以选择了31
         * <p>
         * 总结：为了更好的均匀散列表的下标
         * </p1>
         * 自建对象作为Key
         * 如果我们使用的自己创建的对象，在我们没有重写hashCode()方法的情况下,会调用Object类的hashCode()方法，
         * 而此时返回就是对象的内存地址值，所以如果对象不同，那么通过hashcode()计算出的hashcode就是不同的。
         * <p>
         * JAVA引用类型作为Key
         * 如果是使用java中定义的引用类型例如String，Integer等作为key，
         * 这些类一般都会重写hashCode()方法，有兴趣可以翻看一下对应的源码。
         * 简单来说，Integer类的hashCode()返回的就是Integer值,
         * 而String类型的hashCode()方法稍稍复杂一点，这里展开的方法在下面。
         * <p>
         * 总的来说，hashCode()方法的作用就是要根据不同的key得到不同的hashCode值。
         * <p>
         * 第二步：
         * h >>> 16 将hash值右移16位
         * <p>
         * 第三步：
         * h ^ (h >>> 16) 将hash值的高低16位进行异或（相同得0，不同得1）
         * 假设h值为：1290846991
         * 它的二进制数为：01001100 11110000 11000011 00001111
         * 右移十六位之后：00000000 00000000 01001100 11110000
         * 进行异或操作后：01001100 11110000 10001111 11111111
         * 最终得到的hash值：1290833919
         * <p>
         * <p>
         * 那么问题来了: 明明通过第一步得到的hashcode值就可以作为hash返回，
         * 为什么还要要进行第二步和第三步的操作呢？
         * 答案是为了减少hash冲突！
         * <p>
         * 元素在数组中存放的位置是由什么决定的？
         * <code3>
         * // 将(数组的长度-1)和hash值进行按位 与 操作:
         * i = (n - 1) & hash  // i为数组对应位置的索引  n为当前数组的大小
         *
         * </code3>
         * 还是根据1290846991来计算
         * hash = 1290846991
         * 01001100 11110000 10001111 11111111
         * n-1 = 16-1 =15
         * 00000000 00000000 00000000 00001111
         * hash&n-1
         * =15   00000000 00000000 00000000 00001111
         * 所以元素的下标为15
         *
         * <ps1>
         * <p>
         * 为什么要用h ^ (h >>> 16)操作？
         *      当数组长度n较小时，n-1的二进制数高16位全部位0，
         *      这个时候如果直接和h值进行&（按位与）操作，
         *      那么只能利用到h值的低16位数据，
         *      这个时候会大大增加hash冲突发生的可能性，
         *      因为不同的h值转化为2进制后低16位是有可能相同的，
         * </p>
         * 当进行h ^ (h >>> 16) 操作后，高低16位都进行控制了低位的数值，加大数据复杂性，减小hsah冲突
         * <p>
         * 为什么要用n-1？
         *      因为hashMap规定数组长度为2的整数次幂,n-1得到一个奇数，转化为二进制后低位一定为1
         *      这样n-1与hash进行按位与操作后最低位即可能位0也可能位1，可以充分利用数组空间
         * </p>
         * <p>
         *     为什么hashMap规定数组长度为2的整数次幂
         *     1、因为hash最大范围是40亿个元素，内存中放不下这个散列表，
         *        用之前先对数组的长度取模，得到的余数用于计算元素下标
         *        n-1 & hash
         *     2、便于使用位运算
         *     3、减少hash冲突，控制末尾是1或0
         * </p>
         * </ps1>
         *
         * <ps3>
         * 链表红黑树如何转换？为什么要转换？
         * 此处设计非常经典，参考了时空考虑
         * 链表的时间复杂度为n
         * 红黑树为logn
         * 当元素数量是2的3次方时
         * 红黑树查 2乘3 次
         * 链表查8次
         * 因为数据空间
         * 树节点 是 链表节点的两倍
         * </ps3>
         * <html>
         * 带个福利
         * https://www.cs.usfca.edu/~galles/visualization/Algorithms.html
         * </html>
         * 重写hash算法.
         *
         * @return hash值
         */
        @Override
        public int hashCode() {
            //自建对象默认返回内存地址值
            return super.hashCode();

        }

        /**
         * 重写equals方法.
         *
         * @param obj 比较对象.
         * @return
         */
        @Override
        public boolean equals(Object obj) {
            String hashkey = "123";
            int i = hashkey.hashCode();
            System.out.println("原生equals方法关于自建对象的数值大小：" + new User().hashCode());

            return super.equals(obj);
        }

        /**
         * 重写克隆方法.
         *
         * @return 克隆对象.
         * @throws CloneNotSupportedException 不支持克隆异常
         */
        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }


}
